/**********************************************************************
  Filename    : Intelligent Surveillance System
  Description : ESP32-S3 Camera with HTTP file server and PIR-triggered image capture.
  Auther      : www.freenove.com
  Modification: 2023/12/01
**********************************************************************/
#include "esp_camera.h"
#include <WiFi.h>
#include "sd_read_write.h"
#include <time.h>

// Select camera model
#define CAMERA_MODEL_ESP32S3_EYE // Has PSRAM
#include "camera_pins.h"

// 包含HTTP文件服务器头文件
#include "HttpFileServer.h"

// ====================
// PIR Sensor Settings
// ====================
#define PIR_SENSOR_PIN 3 // SR602连接的GPIO引脚，请根据实际接线修改
bool currentMotionState = false;
bool previousMotionState = false;

// ====================
// WiFi Credentials
// ====================
const char *ssid = "mi";
const char *password = "love1031";

// ====================
// Image Capture Settings
// ====================
#define DEBOUNCE_DELAY 2000 // 防抖延迟，避免连续触发（毫秒）
unsigned long lastCaptureTime = 0;

// Function prototypes
void cameraInit(void);
void startCameraServer();
void setupTime();
String getDateTimeString();
void checkAndManageStorage();
void handlePIRDetection();
void captureAndSaveImage();
bool initSDCard();

void setup()
{
  Serial.begin(115200);
  Serial.setDebugOutput(true);
  Serial.println("Booting Intelligent Surveillance System...");

  // 初始化摄像头
  cameraInit();

  // 初始化SD卡
  if (!initSDCard())
  {
    Serial.println("❌ SD Card initialization failed!");
  }
  else
  {
    Serial.println("✅ SD Card initialized successfully");
  }

  // 初始化PIR传感器
  pinMode(PIR_SENSOR_PIN, INPUT);
  Serial.println("PIR Sensor initialized on GPIO " + String(PIR_SENSOR_PIN));

  // 连接WiFi
  WiFi.begin(ssid, password);
  WiFi.setSleep(false);
  Serial.print("Connecting to WiFi");

  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());

  // 设置时间同步
  setupTime();

  // 启动Web摄像头服务器
  Serial.println("Starting Web Camera Server...");
  startCameraServer();

  // 启动HTTP文件服务器
  // Serial.println("Starting HTTP File Server...");
  // setupHttpFileServer();

  Serial.println("=== System Ready ===");
  Serial.println("Live Camera: http://" + WiFi.localIP().toString());
  // Serial.println("File Manager: http://" + WiFi.localIP().toString() + ":8080");
  Serial.println("Waiting 10 seconds for PIR sensor to stabilize...");
  delay(10000);
  Serial.println("PIR Sensor: Monitoring for motion...");
}

// ==================== SD卡初始化函数 ====================
bool initSDCard()
{
  Serial.println("Mounting SD Card...");

  // 使用与官方示例完全相同的方法
  sdmmcInit();

  // 检查SD卡是否成功挂载
  uint8_t cardType = SD_MMC.cardType();
  if (cardType == CARD_NONE)
  {
    Serial.println("❌ No SD Card detected");
    return false;
  }

  // 显示SD卡信息
  Serial.print("SD Card Type: ");
  switch (cardType)
  {
  case CARD_MMC:
    Serial.println("MMC");
    break;
  case CARD_SD:
    Serial.println("SDSC");
    break;
  case CARD_SDHC:
    Serial.println("SDHC");
    break;
  default:
    Serial.println("UNKNOWN");
    break;
  }

  uint64_t cardSize = SD_MMC.cardSize() / (1024 * 1024);
  Serial.printf("SD Card Size: %lluMB\n", cardSize);

  // 创建必要的目录
  if (!SD_MMC.exists("/images"))
  {
    createDir(SD_MMC, "/images");
    Serial.println("Created /images directory");
  }

  return true;
}

void loop()
{
  // 处理HTTP文件服务器请求
  // handleHttpFileServer();

  // 处理PIR传感器检测
  handlePIRDetection();

  delay(100); // 短暂延迟
}

// ==================== 时间设置功能 ====================
void setupTime()
{
  configTime(8 * 3600, 0, "pool.ntp.org", "time.nist.gov"); // UTC+8
  Serial.print("Syncing time with NTP server");

  time_t now = time(nullptr);
  while (now < 8 * 3600 * 2)
  {
    delay(500);
    Serial.print(".");
    now = time(nullptr);
  }

  Serial.println("");
  struct tm timeinfo;
  localtime_r(&now, &timeinfo);
  Serial.print("Current time: ");
  Serial.println(asctime(&timeinfo));
}

// ==================== 获取日期时间字符串 ====================
String getDateTimeString()
{
  struct tm timeinfo;
  if (!getLocalTime(&timeinfo))
  {
    return "unknown_time";
  }
  char buffer[20];
  // 使用下划线代替冒号和空格
  strftime(buffer, sizeof(buffer), "%Y-%m-%d-%H-%M-%S", &timeinfo);
  return String(buffer);
}

// ==================== 存储空间管理 ====================
void checkAndManageStorage()
{
  uint64_t totalBytes = SD_MMC.totalBytes();
  uint64_t usedBytes = SD_MMC.usedBytes();
  float usedPercentage = (float)usedBytes / totalBytes * 100;

  Serial.printf("SD Card: %.1f%% used (%.1f MB / %.1f MB)\n",
                usedPercentage,
                usedBytes / (1024.0 * 1024.0),
                totalBytes / (1024.0 * 1024.0));

  // 如果使用超过80%，删除最老的图片文件
  if (usedPercentage > 80.0)
  {
    Serial.println("Storage >80%. Cleaning old images...");

    File root = SD_MMC.open("/images");
    if (!root)
    {
      Serial.println("Failed to open images directory");
      return;
    }

    File file = root.openNextFile();
    String oldestFilePath;
    time_t oldestTime = 0;

    // 查找最老的文件
    while (file)
    {
      if (!file.isDirectory() && (String(file.name()).endsWith(".jpg") || String(file.name()).endsWith(".jpeg")))
      {
        time_t fileTime = file.getLastWrite();
        if (oldestTime == 0 || fileTime < oldestTime)
        {
          oldestTime = fileTime;
          oldestFilePath = String(file.path());
        }
      }
      file = root.openNextFile();
    }

    // 删除最老的文件
    if (oldestFilePath != "" && SD_MMC.remove(oldestFilePath.c_str()))
    {
      Serial.println("Deleted: " + oldestFilePath);
    }
    else if (oldestFilePath != "")
    {
      Serial.println("Failed to delete: " + oldestFilePath);
    }
    else
    {
      Serial.println("No image files found to delete");
    }
  }
}

// ==================== PIR传感器检测处理 ====================
void handlePIRDetection()
{
  currentMotionState = digitalRead(PIR_SENSOR_PIN);

  // 检测到运动（从低电平变为高电平）
  if (currentMotionState == HIGH && previousMotionState == LOW)
  {
    Serial.println("🚨 Motion detected by PIR sensor!");

    // 检查SD卡是否可用
    if (SD_MMC.cardType() == CARD_NONE)
    {
      Serial.println("❌ Cannot capture - SD Card not available");
      return;
    }

    // 防抖处理，避免连续触发
    if (millis() - lastCaptureTime > DEBOUNCE_DELAY)
    {
      captureAndSaveImage();
      lastCaptureTime = millis();
    }
    else
    {
      Serial.println("Debounce: Skipping capture due to recent trigger");
    }
  }

  // 运动结束（从高电平变为低电平）
  else if (currentMotionState == LOW && previousMotionState == HIGH)
  {
    Serial.println("✅ Motion ended");
  }

  previousMotionState = currentMotionState;
}

// ==================== 捕获并保存图片 ====================
void captureAndSaveImage()
{
  // 检查SD卡状态
  if (SD_MMC.cardType() == CARD_NONE)
  {
    Serial.println("❌ SD Card not available - cannot capture image");
    return;
  }

  // 获取摄像头帧缓冲区
  camera_fb_t *fb = esp_camera_fb_get();
  if (!fb)
  {
    Serial.println("Camera capture failed");
    return;
  }

  // 生成文件名
  String filename = "/images/motion-" + getDateTimeString() + ".jpg";
  Serial.println("📸 Capturing image: " + filename);

  // 使用官方提供的writejpg函数保存图片
  writejpg(SD_MMC, filename.c_str(), fb->buf, fb->len);

  // 检查文件是否成功保存
  if (SD_MMC.exists(filename.c_str()))
  {
    Serial.println("✅ Image saved successfully (" + String(fb->len) + " bytes)");
    // 检查存储空间
    checkAndManageStorage();
  }
  else
  {
    Serial.println("❌ Failed to save image to SD card");

    // 尝试备选方法
    Serial.println("Trying alternative save method...");
    File file = SD_MMC.open(filename.c_str(), FILE_WRITE);
    if (file)
    {
      size_t written = file.write(fb->buf, fb->len);
      file.close();

      if (written == fb->len)
      {
        Serial.println("✅ Image saved using alternative method");
        checkAndManageStorage();
      }
      else
      {
        Serial.printf("❌ Alternative save failed: wrote %d of %d bytes\n", written, fb->len);
        SD_MMC.remove(filename.c_str());
      }
    }
    else
    {
      Serial.println("❌ Cannot open file for writing");
    }
  }

  // 释放帧缓冲区
  esp_camera_fb_return(fb);
}

// ==================== 摄像头初始化函数 ====================
void cameraInit(void)
{
  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer = LEDC_TIMER_0;
  config.pin_d0 = Y2_GPIO_NUM;
  config.pin_d1 = Y3_GPIO_NUM;
  config.pin_d2 = Y4_GPIO_NUM;
  config.pin_d3 = Y5_GPIO_NUM;
  config.pin_d4 = Y6_GPIO_NUM;
  config.pin_d5 = Y7_GPIO_NUM;
  config.pin_d6 = Y8_GPIO_NUM;
  config.pin_d7 = Y9_GPIO_NUM;
  config.pin_xclk = XCLK_GPIO_NUM;
  config.pin_pclk = PCLK_GPIO_NUM;
  config.pin_vsync = VSYNC_GPIO_NUM;
  config.pin_href = HREF_GPIO_NUM;
  // config.pin_sscb_sda = SIOD_GPIO_NUM;
  // config.pin_sscb_scl = SIOC_GPIO_NUM;
  config.pin_sccb_sda = SIOD_GPIO_NUM;
  config.pin_sccb_scl = SIOC_GPIO_NUM;
  config.pin_pwdn = PWDN_GPIO_NUM;
  config.pin_reset = RESET_GPIO_NUM;
  config.xclk_freq_hz = 20000000;
  config.frame_size = FRAMESIZE_UXGA;
  config.pixel_format = PIXFORMAT_JPEG; // for streaming
  config.grab_mode = CAMERA_GRAB_WHEN_EMPTY;
  config.fb_location = CAMERA_FB_IN_PSRAM;
  config.jpeg_quality = 12;
  config.fb_count = 1;

  // if PSRAM IC present, init with UXGA resolution and higher JPEG quality
  // for larger pre-allocated frame buffer.
  if (psramFound())
  {
    config.jpeg_quality = 10;
    config.fb_count = 2;
    config.grab_mode = CAMERA_GRAB_LATEST;
  }
  else
  {
    // Limit the frame size when PSRAM is not available
    config.frame_size = FRAMESIZE_SVGA;
    config.fb_location = CAMERA_FB_IN_DRAM;
  }

  // camera init
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK)
  {
    Serial.printf("Camera init failed with error 0x%x", err);
    Serial.println("System halted! Please check camera wiring.");
    while (true)
    {
      delay(1000);
    } // 停止程序执行
  }

  sensor_t *s = esp_camera_sensor_get();
  // initial sensors are flipped vertically and colors are a bit saturated
  s->set_vflip(s, 1);      // flip it back
  s->set_brightness(s, 1); // up the brightness just a bit
  s->set_saturation(s, 0); // lower the saturation
}